﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Xml;

namespace Moonlite.Data.Configuration
{
    /// <summary>
    /// Represents a configuration element, which can hold properties and child <see cref="ConfigurationElement"/>s.
    /// </summary>
    public abstract class ConfigurationElement : ConfigurationPropertySyncer, IDumpable, ICanWriteAndRead
    {
        bool inited;
        ConfigurationElementCollection children = new ConfigurationElementCollection();
        ConfigurationElement parent;

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <value>The name.</value>
        public abstract string Name { get; }

        /// <summary>
        /// Gets a value indicating whether this <see cref="ConfigurationElement"/> is initialized.
        /// </summary>
        /// <value><c>true</c> if initialized; otherwise, <c>false</c>.</value>
        public bool Inited
        {
            get { return inited; }
        }

        /// <summary>
        /// Gets or sets the children.
        /// </summary>
        /// <value>The children.</value>
        public ConfigurationElementCollection Children
        {
            get { return children; }
            set { children = value; }
        }

        /// <summary>
        /// Gets or sets the parent.
        /// </summary>
        /// <value>The parent.</value>
        public ConfigurationElement Parent
        {
            get { return parent; }
            set { parent = value; }
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected internal virtual void Init()
        {
            inited = true;
        }

        /// <summary>
        /// Creates a new element based on the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The created element.</returns>
        internal static ConfigurationElement CreateElement(Type type)
        {
            ConfigurationElement element = (ConfigurationElement)Activator.CreateInstance(type, true);
            element.Init();
            return element;
        }

        /// <summary>
        /// Dumps the <see cref="ConfigurationElement"/> into the specified StreamWriter.
        /// </summary>
        /// <param name="writer">The StreamWriter.</param>
        void IDumpable.Dump(StreamWriter writer)
        {
            writer.Write("[ConfigurationElement]");
            foreach (PropertyInfo info in GetType().GetProperties())
            {
                writer.Write("[Property Name=" + info.Name + "; Value=" + info.GetValue(this, null) + "]");
            }
        }

        /// <summary>
        /// Writes the <see cref="ConfigurationElement"/> to the specified node.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="node">The node.</param>
        public virtual void Write(XmlDocument document, XmlNode node)
        {
            Type baseType = typeof(ConfigurationElement);
            Type thisType = GetType();
            XmlNode element = document.CreateNode(XmlNodeType.Element, "element", null);
            XmlAttribute guid = document.CreateAttribute("guid");
            XmlAttribute type = document.CreateAttribute("type");
            guid.Value = Guid.NewGuid().ToString();
            type.Value = thisType.FullName;
            element.Attributes.Append(guid);
            element.Attributes.Append(type);
            Type configurationElementCollectionType = typeof(ConfigurationElementCollection);
            foreach (PropertyInfo info in thisType.GetProperties())
            {
                if (info.Name != "Name" && info.Name != "Inited" && Attribute.IsDefined(info, typeof(ConfigurationPropertyAttribute)) && info.PropertyType.FullName != configurationElementCollectionType.FullName && info.PropertyType.FullName != baseType.FullName)
                {
                    XmlNode infoElement = document.CreateNode(XmlNodeType.Element, "property", null);
                    XmlAttribute keyAttribute = document.CreateAttribute("key");
                    XmlAttribute valueAttribute = document.CreateAttribute("value");
                    XmlAttribute typeAttribute = document.CreateAttribute("type");
                    keyAttribute.Value = info.Name;
                    valueAttribute.Value = info.GetValue(this, null).ToString();
                    typeAttribute.Value = info.PropertyType.FullName;
                    infoElement.Attributes.Append(keyAttribute);
                    infoElement.Attributes.Append(valueAttribute);
                    infoElement.Attributes.Append(typeAttribute);
                    element.AppendChild(infoElement);
                }
            }

            node.AppendChild(element);
        }

        /// <summary>
        /// Reads the <see cref="ConfigurationElement"/> from the specified node.
        /// </summary>
        /// <param name="node">The node.</param>
        public virtual void Read(XmlNode node)
        {
            if (node.Name.ToLower() == "element")
            {
                foreach (XmlNode childNode in node.ChildNodes)
                {
                    if (childNode.Name.ToLower() == "property")
                    {
                        string key = null;
                        object value = null;
                        string type = null;
                        foreach (XmlAttribute attribute in childNode.Attributes)
                        {
                            switch (attribute.Name.ToLower())
                            {
                                case "key":
                                    key = attribute.Value;
                                    break;
                                case "value":
                                    value = attribute.Value;
                                    break;
                                case "type":
                                    type = attribute.Value;
                                    break;
                            }
                        }

                        if (key != null && value != null && type != null)
                        {
                            Type actualType = TypeGetHelpers.GetTypeFromFullName(type);
                            if (actualType == null)
                            {
                                ExceptionHelper.ThrowUnableToGetTypeFromFullNameException(type);
                            }

                            value = TypeConvertHelpers.FromString((string)value, actualType);
                            Type thisType = GetType();
                            PropertyInfo info = thisType.GetProperty(key);
                            if (info != null)
                            {
                                info.SetValue(this, value, null);
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigurationElement"/> class.
        /// </summary>
        protected ConfigurationElement()
            : base()
        {
        }
    }
}
